package com.example.demo.socket;


import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.net.InetSocketAddress;
import java.net.SocketAddress;


/**
 * @author yj
 * @date 2021/4/6 13:38
 */
@Component
public class SocketServer {

    private Logger logger = LoggerFactory.getLogger(SocketServer.class);

    @Autowired
    private SocketServerChannelInitializer socketServerChannelInitializer;

    @Async
    public void start() {
        SocketAddress socketAddress = new InetSocketAddress("0.0.0.0", 8542);
        //主线程
        EventLoopGroup bossGroup = new NioEventLoopGroup(3);
        //工作线程组
        EventLoopGroup workGroup = new NioEventLoopGroup(300);
        ServerBootstrap bootstrap = new ServerBootstrap()
                //定义工作线程
                .group(bossGroup, workGroup)
                //设置为nio类型的channel通道
                .channel(NioServerSocketChannel.class)
                //初始化
                .childHandler(socketServerChannelInitializer)
                //设置端口
                .localAddress(socketAddress)
                //设置通道
                //设置最大连接数，连接最大后则拒绝
                .option(ChannelOption.SO_BACKLOG, 5120)
                //是否开启心跳机制
                .option(ChannelOption.SO_KEEPALIVE, true);
        //SO_RCVBUF、SO_SNDBUF  设置TCP连接的两个buffer尺寸的。每个TCP socket在内核中都有一个发送缓冲区和一个接收缓冲区，TCP的全双工的工作模式以及TCP的滑动窗口便是依赖于这两个独立的buffer以及此buffer的填充状态。
        //SO_SNDBUF             Socket参数，TCP数据发送缓冲区大小。该缓冲区即TCP发送滑动窗口，linux操作系统可使用命令：cat /proc/sys/net/ipv4/tcp_smem 查询其大小。
        //TCP_NODELAY           TCP参数，立即发送数据，默认值为Ture（Netty默认为True而操作系统默认为False）。该值设置Nagle算法的启用，改算法将小的碎片数据连接成更大的报文来最小化所发送的报文的数量，如果需要发送一些较小的报文，则需要禁用该算法。Netty默认禁用该算法，从而最小化报文传输延时。
        //SO_KEEPALIVE          底层TCP协议的心跳机制。Socket参数，连接保活，默认值为False。启用该功能时，TCP会主动探测空闲连接的有效性。可以将此功能视为TCP的心跳机制，需要注意的是：默认的心跳间隔是7200s即2小时。Netty默认关闭该功能。
        //SO_REUSEADDR          Socket参数，地址复用，默认值False。有四种情况可以使用：
                                //(1).当有一个有相同本地地址和端口的socket1处于TIME_WAIT状态时，而你希望启动的程序的socket2要占用该地址和端口，比如重启服务且保持先前端口。
                                //(2).有多块网卡或用IP Alias技术的机器在同一端口启动多个进程，但每个进程绑定的本地IP地址不能相同。
                                //(3).单个进程绑定相同的端口到多个socket上，但每个socket绑定的ip地址不同。(4).完全相同的地址和端口的重复绑定。但这只用于UDP的多播，不用于TCP。
        //SO_LINGER             Socket参数，关闭Socket的延迟时间，默认值为-1，表示禁用该功能。-1表示socket.close()方法立即返回，但OS底层会将发送缓冲区全部发送到对端。0表示socket.close()方法立即返回，OS放弃发送缓冲区的数据直接向对端发送RST包，对端收到复位错误。非0整数值表示调用socket.close()方法的线程被阻塞直到延迟时间到或发送缓冲区中的数据发送完毕，若超时，则对端会收到复位错误。
        //SO_BACKLOG            Socket参数，服务端接受连接的队列长度，如果队列已满，客户端连接将被拒绝。默认值，Windows为200，其他为128。
        //SO_BROADCAST          Socket参数，设置广播模式。
        try {
            ChannelFuture future = bootstrap.bind(socketAddress).sync();
            logger.info("开始监听8542端口");
            future.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            logger.error("监听出现错误，结束监听");
        } finally {
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
            logger.error("监听finally，结束监听");
        }
    }
}